#!/usr/bin/env python2
#-*- conding: utf-8 -*-


import os
import time
import imp
import datetime
import inspect

import traceback
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.schedulers.background import BackgroundScheduler

from Ump.utils import except_run
from Ump.common import thread
from Ump.objs.db import models
from Ump.objs.session_wrapper import _sw
from Ump.schedule.jobs.base import JobBase

from Ump.schedule.triggers.interval import IntervalJob
from Ump.schedule.triggers.crontab import CrontabJob
from Ump.schedule.triggers.date import DateJob

from Ump.common import log
from Ump import defs

job_log = log.init_info_logger('Schedule', logging_file=defs.SCHEDULER_LOG_PATH)


def _add_job_handler(e, *args, **kw):
    if 'with an existing job' in str(e) :
        #job_log.warn('schedule add job error:%s' % e)
        return None
    else:
        traceback.print_exc()
        raise Exception(e)

def _remove_job_handler(e, *args, **kw):
    if 'No job by the id' in str(e) and 'was found' in str(e):
        return None
    else:
        raise Exception(e)


class PluginJobs(object):
    def __init__(self):
        self.jobs_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'jobs')
        self.job_members = {}
        self._scan_folder()
    
    def _load_module(self, mpath, mname=None):
        module_name = inspect.getmodulename(mpath) if not mname else mname
        search_path = [os.path.dirname(mpath)] if os.path.isfile(mpath) else [mpath]
        base_module_name = module_name.split('.')[-1]
        (mfile, mpname, mdesc) = imp.find_module(base_module_name, search_path)
        try:
            mobj = imp.load_module(module_name, mfile, mpname, mdesc)
            self._parse_plugins(mobj)
        finally:
            mfile.close()

    def _scan_folder(self):
        for root, dirs, files in os.walk(self.jobs_folder):
            for f in files:
                if f.endswith('.py'): self._load_module(os.path.join(root, f))

    def _parse_plugins(self, mobj):
        members = inspect.getmembers(mobj)
        for (name, member) in members:
            if not inspect.isclass(member) or not issubclass(member, JobBase):
                continue

            name = member.__name__
            if name == "JobBase":
                continue
            
            self.job_members[name] = member()


class SchedulerManage(PluginJobs): 

    scheduler = BackgroundScheduler()
    def __init__(self):
        super(SchedulerManage, self).__init__()
        self.interval_trigger = IntervalJob() 
        self.crontab_trigger = CrontabJob()
        self.date_trigger = DateJob()
        self.init_jobs()

    @except_run(exc_handler=_add_job_handler)
    def add_job(self, sql_job):
        job_log.info("scheduler add job: %s %s" % (sql_job.class_name, sql_job.id))
        member = self._get_member(sql_job.class_name)

        args = (member.run, sql_job, self.scheduler)
        if sql_job.schedule_type == 'interval':

            self.interval_trigger.add_job(*args)
        elif sql_job.schedule_type == 'cron':
            self.crontab_trigger.add_job(*args)
        elif sql_job.schedule_type == 'date':
            self.date_trigger.add_job(*args)

    @except_run(exc_handler=_remove_job_handler)
    def remove_job(self, sql_job_id): 
        self.scheduler.remove_job(str(sql_job_id))
        #job_log.warn('schedule remove job error:%s' % e)

    def reschedule_job(self, sql_job):

        job_id = str(sql_job.id)
        if not sql_job.enabled:
            self.remove_job(job_id)
            return   

        try:
            self._update_job(sql_job)
        except Exception, e:
            if 'No job by the id' in str(e) and 'was found' in str(e):
                self.add_job(sql_job)
            else:
                raise Exception(e)


    def _update_job(self, sql_job):
        args = (sql_job, self.scheduler)

        if sql_job.schedule_type == 'interval':

            self.interval_trigger.update_job(*args)
        elif sql_job.schedule_type == 'cron':
            self.crontab_trigger.update_job(*args)
        elif sql_job.schedule_type == 'date':
            self.date_trigger.update_job(*args)
        
    def _get_member(self, module_name):
        return self.job_members[module_name]

    def init_jobs(self):
        sql_jobs = _sw.get_list(model=models.ScheduleJob, spec={})
        for sql_job in sql_jobs:
            if not sql_job.enabled:
                continue
        
            self.add_job(sql_job)
    
    @thread.AsyncThread
    def start_in_thread(self):
        self.scheduler.start()

    def start(self):
        job_log.info('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
#        log.info('start scheduler success')
        try:
            self.scheduler.start()
        except (KeyboardInterrupt, SystemExit):
            self.scheduler.shutdown() 

_scheduler = None
_app = None
def init_scheduler(app):
    global _scheduler
    global _app
    if not _scheduler:
        _app = app
        _scheduler = SchedulerManage()
    return _scheduler


if __name__ == '__main__':
    schedulerm = SchedulerManage()
    schedulerm.start_in_thread()
#E    schedulerm.start()
#E    schedulerm._scan_folder()
